package com.web.pay;

import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import com.xkk.utils.Bean2Map;
import com.xkk.utils.GSONUtil;
import com.xkk.utils.MapUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;

/**
 * 微信支付
 *
 * @author xukk
 * @date 2017/7/4
 */
@Slf4j
public class WxPayManage {
    public static WxPayManage mg = new WxPayManage();
    private static Map<String, WXPay> wxPays;

    /**
     * 支付
     * body 商品描述 必须
     * out_trade_no 商户订单号 必须
     * total_fee 总金额 必须
     * spbill_create_ip IP地址 必须
     * trade_type 交易类型 JSAPI 、 APP 、NATVIE 必须
     * <p>
     * device_info设备号 非必须  自定义参数，可以为终端设备号(门店号或收银设备ID)，PC网页或公众号内支付可以传"WEB"
     * fee_type 标价币种 非必须
     * product_id 产品ID 非必须
     * openid 非必须
     *
     * @param wxPayDto
     * @return
     */
    public PayResponse wxPay(WxPayDto wxPayDto) throws Exception {
        WXPay wxPay = getWxapy(wxPayDto);
        HashMap<String, String> data = new HashMap<String, String>();
        data.put("body", wxPayDto.getBody());
        data.put("out_trade_no", wxPayDto.getOut_trade_no());
        if (StringUtils.isNotBlank(wxPayDto.getDevice_info())) {
            data.put("device_info", wxPayDto.getDevice_info());
        }
        if (StringUtils.isNotBlank(wxPayDto.getFee_type())) {
            data.put("fee_type", wxPayDto.getFee_type());
        }
        data.put("total_fee", wxPayDto.getTotal_fee());
        data.put("time_expire", wxPayDto.getTime_expire());
        data.put("spbill_create_ip", wxPayDto.getSpbill_create_ip());
        if (StringUtils.isBlank(wxPayDto.getNotify_url())) {
            return PayResponse.custom().failure("notify_url is blank");
        }
        data.put("notify_url", wxPayDto.getNotify_url());
        data.put("trade_type", wxPayDto.getTrade_type());
        if (StringUtils.isNotBlank(wxPayDto.getProduct_id())) {
            data.put("product_id", wxPayDto.getProduct_id());
        }
        if (StringUtils.isNotBlank(wxPayDto.getOpenid())) {
            data.put("openid", wxPayDto.getOpenid());
        }
        try {
            log.info("wx pay data->{}", data);
            Map<String, String> r = wxPay.unifiedOrder(data);
            UnifiedOrderResult unifiedOrderResult = (UnifiedOrderResult) Bean2Map.convertMap(UnifiedOrderResult.class, r);
            log.info("wx pay unifiedRefundResult->{}", GSONUtil.toJson(unifiedOrderResult));
            if (unifiedOrderResult.isSuccess()) {
                if (StringUtils.equals(wxPayDto.getTrade_type(), "APP")) {
                    APPPay appPay = new APPPay(unifiedOrderResult.getAppid(), unifiedOrderResult.getMch_id(), unifiedOrderResult.getPrepay_id(), wxPayDto.getSignKey());
                    return PayResponse.custom().addParams(appPay).ok();
                } else if (StringUtils.equals(wxPayDto.getTrade_type(), "JSAPI")) {
                    WCPay wcPay = new WCPay(unifiedOrderResult.getAppid(), unifiedOrderResult.getPrepay_id(), wxPayDto.getSignKey());
                    return PayResponse.custom().addParams(wcPay).ok();
                }
                return PayResponse.custom().addParams(unifiedOrderResult.getReturn_msg()).ok();
            } else {
                return PayResponse.custom().addParams(unifiedOrderResult).failure();
            }


        } catch (Exception e) {
            e.printStackTrace();
            log.warn("wx pay error->{}", e.getMessage());
            throw new RuntimeException(e.getMessage());
        }

    }


    /**
     * 订单查询
     *
     * @param wxPayDto
     * @return
     */
    public PayResponse orderquery(WxPayDto wxPayDto) throws Exception {
        WXPay wxPay = getWxapy(wxPayDto);
        HashMap<String, String> data = new HashMap<String, String>();
        data.put("out_trade_no", wxPayDto.getOut_trade_no());
        try {
            //log.info("wx pay data->{}", data);
            //Map<String, String> r = wxPay.unifiedOrder(data);
            // UnifiedOrderResult unifiedOrderResult = (UnifiedOrderResult) Bean2Map.convertMap(UnifiedOrderResult.class, r);
            // log.info("wx pay unifiedRefundResult->{}", GSONUtil.toJson(unifiedOrderResult));
            // if (unifiedOrderResult.isSuccess()) {
            Map<String, String> result = wxPay.orderQuery(data);
            return PayResponse.custom().addParams(result).ok();
            // } else
            // return PayResponse.custom().addParams(unifiedOrderResult).failure();

        } catch (Exception e) {
            e.printStackTrace();
            //  log.warn("wx pay error->{}", e.getMessage());
            throw new RuntimeException(e.getMessage());
        }

    }

    /**
     * url通知返回校验          Map map = MapUtil.sortAsc(MapUtil.xmlToMap(string));
     *
     * @param wxPayDto
     * @param map
     * @return
     */
    public PayResponse wxValid(WxPayDto wxPayDto, Map map) {
        log.info("wxvalid:wxpayDto->{},map->{}", wxPayDto, map);
        WXPay wxPay = getWxapy(wxPayDto);
        try {
            if (map.containsKey(WXPayConstants.FIELD_SIGN_TYPE)) {
                if (wxPay.isPayResultNotifySignatureValid(map)) {
                    return PayResponse.custom().ok();
                } else {
                    return PayResponse.custom().failure();
                }
            } else {
                if (wxPay.isPayResultNotifySignatureValid(map, WXPayConstants.SignType.HMACSHA256)) {
                    return PayResponse.custom().ok();
                } else {
                    return PayResponse.custom().failure();
                }
            }
        } catch (Exception e) {
            return PayResponse.custom().failure(e.getMessage());
        }
    }

    /**
     * 退款
     * total_fee 交易金额  必须
     * out_refund_no 系统退单号 必须
     * out_trade_no  系统订单号  必须
     * refund_fee 退款金额  必须
     * refund_fee_type 金额类型 非必须
     *
     * @param wxPayDto
     * @return
     */
    public PayResponse wxRefund(WxPayDto wxPayDto) {
        WXPay wxPay = getWxapy(wxPayDto);
        HashMap<String, String> data = new HashMap<String, String>();
        if (wxPayDto.getOut_trade_no() != null) {
            data.put("out_trade_no", wxPayDto.getOut_trade_no());
        }
        if (wxPayDto.getTransaction_id() != null) {
            data.put("transaction_id", wxPayDto.getTransaction_id());
        }
        data.put("out_refund_no", wxPayDto.getOut_refund_no());
        data.put("total_fee", wxPayDto.getTotal_fee());
        data.put("refund_fee", wxPayDto.getRefund_fee());
        if (StringUtils.isNotBlank(wxPayDto.getRefund_fee_type())) {
            data.put("refund_fee_type", wxPayDto.getRefund_fee_type());
        }
        try {
            Map<String, String> r = wxPay.refund(data);
            UnifiedRefundResult unifiedRefundResult = (UnifiedRefundResult) Bean2Map.convertMap(UnifiedRefundResult.class, r);
            log.info("unifiedRefundResult->{}", GSONUtil.toJson(unifiedRefundResult));
            if (unifiedRefundResult.isSuccess()) {
                return PayResponse.custom().failure(unifiedRefundResult.getReturn_msg());
            } else {
                return PayResponse.custom().addParams(unifiedRefundResult).ok();
            }
        } catch (Exception e) {
            log.warn("wx pay error->{}", e.getMessage());
            return PayResponse.custom().failure(e.getMessage());
        }
    }

    @SneakyThrows(Exception.class)
    public WXPay getWxapy(WxPayDto wxPayDto) {
        WXPay wxPay = null;
        if (wxPays != null) {
            for (Map.Entry<String, WXPay> entry : wxPays.entrySet()) {

                if (entry.getKey().equals(wxPayDto.getAppid() + "|" + wxPayDto.getMch_id())) {
                    wxPay = entry.getValue();
                    break;
                }

            }
        }
        if (wxPays == null || wxPay == null) {
            if (wxPays == null) {
                wxPays = new HashMap<>();
            }
            wxPay = new WXPay(new WxPayConfigImpl(wxPayDto.getAppid(), wxPayDto.getMch_id(), wxPayDto.getSignKey()));
            wxPays.put(wxPayDto.getAppid() + "|" + wxPayDto.getMch_id(), wxPay);
        }
        return wxPay;
    }


    /**
     * 微信内网页支付
     *
     * @author qsyang
     * @version 1.0
     */
    public static class WCPay {
        /**
         * 公众号Id
         */
        private final String appId;
        /**
         * 时间戳
         */
        private final String timeStamp;
        /**
         * 随机字符串
         */
        private final String nonceStr;
        /**
         * 订单详情扩展字符串 统一下单接口返回的prepay_id参数值，提交格式如：prepay_id=
         ****/
        private final String packages;
        /**
         * 签名方式 签名算法，暂支持MD5
         */
        private final String signType = "MD5";
        /**
         * 签名
         */
        private final String paySign;

        /**
         * getBrandWCPayRequest
         *
         * @param appId       公众号Id
         * @param prepay_id   预下单Id
         * @param paternerKey 商户密钥
         */
        public WCPay(String appId, String prepay_id, String paternerKey) {
            this.appId = appId;
            this.timeStamp = System.currentTimeMillis() / 1000 + "";
            this.nonceStr = java.util.UUID.randomUUID().toString().substring(0, 15);
            this.packages = "prepay_id=" + prepay_id;
            //对提交的参数进行签名
            Map<String, String> paySignMap = new HashMap<String, String>();
            paySignMap.put("appId", this.appId);
            paySignMap.put("timeStamp", this.timeStamp);
            paySignMap.put("nonceStr", this.nonceStr);
            paySignMap.put("package", this.packages);
            paySignMap.put("signType", this.signType);
            //签名
            this.paySign = getSign(paySignMap, paternerKey);
        }

        public String getAppId() {
            return appId;
        }

        public String getTimeStamp() {
            return timeStamp;
        }

        public String getNonceStr() {
            return nonceStr;
        }

        public String getPackages() {
            return packages;
        }

        public String getSignType() {
            return signType;
        }

        public String getPaySign() {
            return paySign;
        }


    }

    public static String getSign(Map<String, String> M, String paternerKey) {
        //1.1 对所有待签名参数按照字段名的ASCII 码从小到大排序（字典序）
        Map<String, String> sortParams = MapUtil.sortAsc(M);
        //1.2 使用URL键值对的格式
        String string1 = MapUtil.mapJoin(sortParams, false);
        //拼接签名字符串
        String stringSignTemp = string1 + "&key=" + paternerKey;
        System.out.println(stringSignTemp);
        //2.对string1进行MD5签名
        String sign = DigestUtils.md5Hex(stringSignTemp).toUpperCase();
        return sign;
    }

    public static class APPPay {

        private final String partnerId;
        private final String appId; //公众号Id
        private final String timeStamp;         //时间戳
        private final String nonceStr;          //随机字符串
        private final String packages = "Sign=WXPay";          //订单详情扩展字符串 统一下单接口返回的prepay_id参数值，提交格式如：prepay_id=***
        private final String paySign;           //签名
        private final String prepay_id;

        /**
         * @param partnerId
         * @param prepay_id
         * @param paternerKey
         */
        public APPPay(String appId, String partnerId, String prepay_id, String paternerKey) throws Exception {
            this.appId = appId;
            this.timeStamp = System.currentTimeMillis() / 1000 + "";
            this.prepay_id = prepay_id;
            this.partnerId = partnerId;
            this.nonceStr = java.util.UUID.randomUUID().toString().substring(0, 15);
            //对提交的参数进行签名
            Map<String, String> paySignMap = new HashMap<String, String>();
            paySignMap.put("appid", this.appId);
            paySignMap.put("prepayid", this.prepay_id);
            paySignMap.put("partnerid", this.partnerId);
            paySignMap.put("timestamp", this.timeStamp);
            paySignMap.put("noncestr", this.nonceStr);
            paySignMap.put("package", this.packages);

            //签名
            //this.paySign =getSign(paySignMap, paternerKey);
            this.paySign = WXPayUtil.generateSignature(paySignMap, paternerKey, WXPayConstants.SignType.HMACSHA256);
        }


        public String getTimeStamp() {
            return timeStamp;
        }

        public String getNonceStr() {
            return nonceStr;
        }

        public String getPackages() {
            return packages;
        }

        public String getPartnerId() {
            return partnerId;
        }

        public String getPrepay_id() {
            return prepay_id;
        }

        public String getPaySign() {
            return paySign;
        }

        public String getAppId() {
            return appId;
        }
    }

}
